import math

#The basic property of this algorithim is to apply the prisoners dilemma solution when faced with a slacker and thus slack
#or apply the nash equilibrium solution when faced with a a hunter and thus hunt
#the algorithim also does try to look for optimal moments when slacking or hunting would be advantageous in order
#to maintain a higher food supply or a higher reputation
#in other words it does not always serve to hunt when faced with someone who hunts more often than usual (when compared to the reputation 
#of other members of the tribe), since it would serve better to slack at this point and increase the food supply
#likewise, it does not always make sense to slack even when faced with someone whose reputation is low, since applying this in many
#cases can eventually lower one's reputation significantly causing other tribe members to avoid hunting when coming into 
#contact with such a player, thereby lowering this player's food supply anyway

#a balanced approach is the best, one that takes into account when the food supply is diminshing greatly over a period of time 
#and accounting for this by choosing to slack more at this point and then choosing to hunt when the food supply is relatively constant or increasing
#this algorithim tries to maintain a balanced approach in such a manner throughout as much of the game as possible
#accounting for shifts using basic mathematical stastical analysis and then switching to game theory during the decision making process

initial_numPlayers = None
switch = True

current_foodPlot = []
a = None
b = None

number_huntersPlot = []
awardPlot = []
mPlot = []

rep_tracker = []
my_rep = None
upper_bound = None
lower_bound = None
optimal_rep = None

def m_averageValue(num_players):
	average = (math.pow(num_players, 2) / 3) - (num_players / 2) 
	return average

def linear_regression(plot1, plot2=None):
	global a
	global b

	if plot2 == None:
		size = len(plot1)
		sum_x = 0
		sum_y = 0 
		sum_xy = 0
		sum_xSquared = 0
		for x in range(0, size):
			sum_x += x
		for x in plot1:
			sum_y += x
		index = 0
		for x in range(0, size):
			product = plot1[index] * x
			sum_xy += product
			index += 1
		for x in range(0, size):
			sum_xSquared += math.pow(x, 2)

		b = (sum_y * sum_xSquared - sum_x * sum_xy) / (size * sum_xSquared - math.pow(sum_x, 2))
		a = (size * sum_xy - sum_x * sum_y) / (size * sum_xSquared - math.pow(sum_x, 2))

def linear_function(x):
	return a * x + b

def correlation(plot, functionPlot):
	ssTot = 0
	ssRes = 0
	mean = average(plot)

	for x in plot:
		difference = x - mean
		ssTot += math.pow(difference, 2)

	index = 0
	for x in plot:
		difference = x - functionPlot[index]
		ssRes += math.pow(difference, 2)
		index += 1

	return 1 - (ssRes / ssTot)

def mode(values):
	count_set = []
	for x in values:
		if len(count_set) != 0:
			found = False
			for y in count_set:
				if not found:
					if x == y[0]:
						y[1] += 1
						found = True
			if not found:
				pair = [x, 1]
				count_set.append(pair)

		else:
			pair = [x, 1]
			count_set.append(pair)

	current_mode = None
	max_count = 0
	for z in count_set:
		if z[1] > max_count:
			current_mode = z[0]
			max_count = z[1]
		elif z[1] == max_count:
			if z[0] > current_mode:
				current_mode = z[0]

	if max_count != 0 and max_count != 1:
		return current_mode

	else:
		return None

def average(values):
	n = len(values)
	total = 0
	for x in values:
		total += x

	return total / n

def maximum(values):
	current_max = 0
	for x in values:
		if x > current_max:
			current_max = x

	return current_max

def minimum(values):
	current_min = 1
	for x in values:
		if x < current_min:
			current_min = x

	return current_min

def std(avg, values):
	N = len(values)
	total = 0
	for x in values:
		y = x - avg
		total += math.pow(y, 2)

	std_squared = total / N

	return math.sqrt(std_squared)

def slack_strategy(player_reputations, hunt_decisions, m, current_stage):
	global number_huntersPlot
	global mPlot
	global awardPlot
	global optimal_rep
	global upper_bound

	m_upper = None
	m_lower = None

	num_players = len(player_reputations)
	m_avg = m_averageValue(num_players)

	valid_mValues = []

	index = 0
	for x in mPlot:
		if awardPlot[index]:
			valid_mValues.append(x)
		index += 1

	max_m = maximum(valid_mValues)
	avg_m = average(valid_mValues)
	min_m = minimum(valid_mValues)

	if max_m < m_avg and min_m < m_avg:
		max_count = 0
		for x in valid_mValues:
			if x >= max_m and x <= m_avg:
				max_count += 1

		avg_count = 0
		for x in valid_mValues:
			if x >= avg_m and x <= m_avg:
				avg_count += 1

		sig_count = len(valid_mValues) * .5
		
		if max_count >= sig_count:
			m_upper = m_avg
			m_lower = max_m

		elif avg_count >= sig_count:
			m_upper = m_avg
			m_lower = avg_m

		else:
			m_upper = m_avg
			m_lower = min_m

	elif max_m > m_avg and min_m < m_avg:
		avg_count = 0
		for x in valid_mValues:
			if x >= avg_m and x <= m_avg:
				avg_count += 1

		sig_count = len(valid_mValues) * .5

		if avg_count >= sig_count:
			m_upper = m_avg
			m_lower = avg_m		

		else:
			m_upper = m_avg
			m_lower = min_m

	elif max_m > m_avg and min_m > m_avg:
		m_upper = m_avg
		m_lower = 0

	if current_stage == "Early stage":
		hunter_functionPlot = []
		hunter_size = len(number_huntersPlot)
		linear_regression(number_huntersPlot)
		for x in range(0, hunter_size):
			y = linear_function(x)
			hunter_functionPlot.append(y)
		R = math.sqrt(correlation(number_huntersPlot, hunter_functionPlot))

		upper = upper_bound
		lower = optimal_rep
		if R > .6:
			huntersLeaving_threshold = -(1 / 10) * num_players 
			if a < huntersLeaving_threshold:
				if upper <= .9:
					upper += .1
				if lower <= .9:
					lower += .1

		if m <= m_upper and m >= m_lower:
			if upper >= 0:
				upper -= .1
			if lower >= 0:
				lower -= .1

		for r in player_reputations:
			if r <= upper and r >= lower:
				decision = 'h'
				hunt_decisions.append(decision)
			else:
				decision = 's'
				hunt_decisions.append(decision)

	elif current_stage == "Middle stage":
		hunter_functionPlot = []
		hunter_size = len(number_huntersPlot)
		linear_regression(number_huntersPlot)
		for x in range(0, hunter_size):
			y = linear_function(x)
			hunter_functionPlot.append(y)
		R = math.sqrt(correlation(number_huntersPlot, hunter_functionPlot))

		upper = upper_bound
		lower = lower_bound
		if R > .6:
			huntersLeaving_threshold = -(1 / 100) * num_players 
			if a < huntersLeaving_threshold:
				if upper <= .9:
					upper += .1
				if lower <= .9:
					lower += .1

		if m <= m_upper and m >= m_lower:
			if upper >= 0:
				upper -= .1
			if lower >= 0:
				lower -= .1

		for r in player_reputations:
			if r <= upper and r >= lower:
				decision = 'h'
				hunt_decisions.append(decision)
			else:
				decision = 's'
				hunt_decisions.append(decision)
	elif current_stage == "End stage":
		hunter_functionPlot = []
		hunter_size = len(number_huntersPlot)
		linear_regression(number_huntersPlot)
		for x in range(0, hunter_size):
			y = linear_function(x)
			hunter_functionPlot.append(y)
		R = math.sqrt(correlation(number_huntersPlot, hunter_functionPlot))

		upper = upper_bound
		lower = lower_bound
		if R > .6:
			huntersLeaving_threshold = -(1 / 1000) * num_players 
			if a < huntersLeaving_threshold:
				if upper <= .9:
					upper += .1
				if lower <= .9:
					lower += .1

		if m <= m_upper and m >= m_lower:
			if upper >= 0:
				upper -= .1
			if lower >= 0:
				lower -= .1

		for r in player_reputations:
			if r <= upper and r >= lower:
				decision = 'h'
				hunt_decisions.append(decision)
			else:
				decision = 's'
				hunt_decisions.append(decision)

def hunt_strategy(player_reputations, hunt_decisions, m, current_stage):
	global number_huntersPlot
	global mPlot
	global awardPlot
	global upper_bound
	global lower_bound

	m_upper = None
	m_lower = None

	num_players = len(player_reputations)
	m_avg = m_averageValue(num_players)

	valid_mValues = []

	index = 0
	for x in mPlot:
		if awardPlot[index]:
			valid_mValues.append(x)
		index += 1

	max_m = maximum(valid_mValues)
	avg_m = average(valid_mValues)
	min_m = minimum(valid_mValues)

	if max_m < m_avg and min_m < m_avg:
		max_count = 0
		for x in valid_mValues:
			if x >= max_m and x <= m_avg:
				max_count += 1

		avg_count = 0
		for x in valid_mValues:
			if x >= avg_m and x <= m_avg:
				avg_count += 1

		sig_count = len(valid_mValues) * .5
		
		if max_count >= sig_count:
			m_upper = m_avg
			m_lower = max_m

		elif avg_count >= sig_count:
			m_upper = m_avg
			m_lower = avg_m

		else:
			m_upper = m_avg
			m_lower = min_m

	elif max_m > m_avg and min_m < m_avg:
		avg_count = 0
		for x in valid_mValues:
			if x >= avg_m and x <= m_avg:
				avg_count += 1

		sig_count = len(valid_mValues) * .5

		if avg_count >= sig_count:
			m_upper = m_avg
			m_lower = avg_m		

		else:
			m_upper = m_avg
			m_lower = min_m

	elif max_m > m_avg and min_m > m_avg:
		m_upper = m_avg
		m_lower = 0

	if current_stage == "Early stage":
		hunter_functionPlot = []
		hunter_size = len(number_huntersPlot)
		linear_regression(number_huntersPlot)
		for x in range(0, hunter_size):
			y = linear_function(x)
			hunter_functionPlot.append(y)
		R = math.sqrt(correlation(number_huntersPlot, hunter_functionPlot))

		upper = upper_bound
		lower = lower_bound
		if R > .6:
			huntersLeaving_threshold = -(1 / 10) * num_players 
			if a < huntersLeaving_threshold:
				if upper <= .9:
					upper += .1
				if lower <= .9:
					lower += .1

		if m <= m_upper and m >= m_lower:
			if upper >= 0:
				upper -= .1
			if lower >= 0:
				lower -= .1

		for r in player_reputations:
			if r <= upper and r >= lower:
				decision = 'h'
				hunt_decisions.append(decision)
			else:
				decision = 's'
				hunt_decisions.append(decision)

	elif current_stage == "Middle stage":
		hunter_functionPlot = []
		hunter_size = len(number_huntersPlot)
		linear_regression(number_huntersPlot)
		for x in range(0, hunter_size):
			y = linear_function(x)
			hunter_functionPlot.append(y)
		R = math.sqrt(correlation(number_huntersPlot, hunter_functionPlot))

		upper = upper_bound
		lower = lower_bound
		if R > .6:
			huntersLeaving_threshold = -(1 / 100) * num_players 
			if a < huntersLeaving_threshold:
				if upper <= .9:
					upper += .1
				if lower <= .9:
					lower += .1

		if m <= m_upper and m >= m_lower:
			if upper >= 0:
				upper -= .1
			if lower >= 0:
				lower -= .1

		for r in player_reputations:
			if r <= upper and r >= lower:
				decision = 'h'
				hunt_decisions.append(decision)
			else:
				decision = 's'
				hunt_decisions.append(decision)
	elif current_stage == "End stage":
		hunter_functionPlot = []
		hunter_size = len(number_huntersPlot)
		linear_regression(number_huntersPlot)
		for x in range(0, hunter_size):
			y = linear_function(x)
			hunter_functionPlot.append(y)
		R = math.sqrt(correlation(number_huntersPlot, hunter_functionPlot))

		upper = upper_bound
		lower = lower_bound
		if R > .6:
			huntersLeaving_threshold = -(1 / 1000) * num_players 
			if a < huntersLeaving_threshold:
				if upper <= .9:
					upper += .1
				if lower <= .9:
					lower += .1

		if m <= m_upper and m >= m_lower:
			if upper >= 0:
				upper -= .1
			if lower >= 0:
				lower -= .1

		for r in player_reputations:
			if r <= upper and r >= lower:
				decision = 'h'
				hunt_decisions.append(decision)
			else:
				decision = 's'
				hunt_decisions.append(decision)

def hunt_choices(round_number, current_food, current_reputation, m,  player_reputations):
	hunt_decisions = []
	size = len(player_reputations)

	global my_rep 
	global current_foodPlot
	global initial_numPlayers
	global switch
	global a

	my_rep = current_reputation

	current_foodPlot.append(current_food)

	if initial_numPlayers == None:
		initial_numPlayers = size

	first_checkPoint = (3 / 4) * initial_numPlayers
	second_checkPoint = (1 / 4) * initial_numPlayers

	if size <= initial_numPlayers and size >= first_checkPoint:
		stage = "Early stage"
		if round_number == 1:
			while len(hunt_decisions) <= size:
				hunt_decisions.append('h')
		elif round_number == 2:
			decision = None
			for r in player_reputations:
				if r == 1:
					decision = 'h'
					hunt_decisions.append(decision)
					l = [r, decision]
					rep_tracker.append(l)
				else:
					decision = 's'
					l = [r, decision]
					hunt_decisions.append(decision)
					rep_tracker.append(l)
		else:
			if round_number % 10:
				linear_regression(current_foodPlot)
				functionPlot = []
				for x in range(0, len(current_foodPlot)):
					y = linear_function(x)
					functionPlot.append(y)

				Rsquared = correlation(current_foodPlot, functionPlot)
				R = math.sqrt(Rsquared)
				if R >= .6:
					loss_threshold = -3 * size * (1 / 4)
					if a <= loss_threshold:
						switch = False
						slack_strategy(player_reputations, hunt_decisions, m, stage)
						current_foodPlot = []
						functionPlot = []
					else:
						switch = True
						hunt_strategy(player_reputations, hunt_decisions, m, stage)
						current_foodPlot = []
						functionPlot = []
				else:
					switch = True
					hunt_strategy(player_reputations, hunt_decisions, m, stage)

			else:
				if not switch:
					slack_strategy(player_reputations, hunt_decisions, m, stage)

				if switch:
					hunt_strategy(player_reputations, hunt_decisions, m, stage)

	elif size < first_checkPoint and size >= second_checkPoint:
		stage = "Middle stage"
		if round_number % 5:
			linear_regression(current_foodPlot)
			functionPlot = []
			for x in range(0, len(current_foodPlot)):
				y = linear_function(x)
				functionPlot.append(y)

			Rsquared = correlation(current_foodPlot, functionPlot)
			R = math.sqrt(Rsquared)
			if R >= .6:
				loss_threshold = -3 * size * (1 / 8)
				if a <= loss_threshold:
					switch = False
					slack_strategy(player_reputations, hunt_decisions, m, stage)
					current_foodPlot = []
					functionPlot = []
				else:
					switch = True
					hunt_strategy(player_reputations, hunt_decisions, m, stage)
					current_foodPlot = []
					functionPlot = []
			else:
				switch = True
				hunt_strategy(player_reputations, hunt_decisions, m, stage)

		else:
			if not switch:
				slack_strategy(player_reputations, hunt_decisions, m, stage)

			if switch:
				hunt_strategy(player_reputations, hunt_decisions, m, stage)

	elif size < second_checkPoint and size >= 0:
		stage = "End stage"
		if round_number % 3:
			linear_regression(current_foodPlot)
			functionPlot = []
			for x in range(0, len(current_foodPlot)):
				y = linear_function(x)
				functionPlot.append(y)

			Rsquared = correlation(current_foodPlot, functionPlot)
			R = math.sqrt(Rsquared)
			if R >= .6:
				loss_threshold = -3 * size * (1 / 12)
				if a <= loss_threshold:
					switch = False
					slack_strategy(player_reputations, hunt_decisions, m, stage)
					current_foodPlot = []
					functionPlot = []
				else:
					switch = True
					hunt_strategy(player_reputations, hunt_decisions, m, stage)
					current_foodPlot = []
					functionPlot = []
			else:
				switch = True
				hunt_strategy(player_reputations, hunt_decisions, m, stage)

		else:
			if not switch:
				slack_strategy(player_reputations, hunt_decisions, m, stage)

			if switch:
				hunt_strategy(player_reputations, hunt_decisions, m, stage)

	return hunt_decisions

def hunt_outcomes(food_earnings):
	global rep_tracker
	global optimal_rep
	global my_rep
	global upper_bound
	global lower_bound
	rep_List = []

	for x in food_earnings:
		for y in rep_tracker:
			if food_earnings.index(x) == rep_tracker.index(y):
				if y[1] == 'h':
					if x == 0:
						rep_List.append(y[0])
				else:
					if x == 1:
						rep_List.append(y[0])

	rep_tracker = []
	
	if len(rep_List) != 0:
		mode_rep = mode(rep_List)
		average_rep = average(rep_List)
		std_rep = std(average_rep, rep_List)
		max_rep = maximum(rep_List)
		min_rep = minimum(rep_List)
		upper_outlier = False
		lower_outlier = False
		sig_mode = False 
		sig_size = len(rep_List) * (1 / 5)
		mode_count = 0
		for x in rep_List:
			if mode_rep == x:
				mode_count += 1
		if mode_count >= sig_size:
			sig_mode = True

		one_std_above = average_rep + std_rep
		one_std_below = average_rep - std_rep
		two_std_above = average_rep + 2 * std_rep
		two_std_below = average_rep - 2 * std_rep
		three_std_above = average_rep + 3 * std_rep
		three_std_below = average_rep - 3 * std_rep

		if max_rep <= three_std_above:
			upper_bound = max_rep

		else:
			if three_std_above <= 1:
				upper_bound = three_std_above
				upper_outlier = True
			elif two_std_above <= 1:
				upper_bound = two_std_above
				upper_outlier = True
			elif one_std_above <= 1:
				upper_bound = one_std_above
				upper_outlier = True
			else:
				upper_bound = max_rep
				upper_outlier = True

		if min_rep >= three_std_below:
			lower_bound = min_rep

		else:
			if three_std_below >= 0:
				lower_bound = three_std_below
				lower_outlier = True
			elif two_std_below >= 0:
				lower_bound = two_std_below
				lower_outlier = True
			elif one_std_below >= 0:
				lower_bound = one_std_below
				lower_outlier = True
			else:
				lower_bound = min_rep
				lower_outlier = True

		if mode_rep != None:
			if sig_mode:
				if not lower_outlier:
					if not upper_outlier:
						if my_rep > max_rep:
							if my_rep <= three_std_above:
								optimal_rep = my_rep
							else:
								optimal_rep = max_rep
						else:
							optimal_rep = max_rep

					else:
						if my_rep <= three_std_above and my_rep >= two_std_above:
							if my_rep > mode_rep:
								optimal_rep = my_rep
							else:
								optimal_rep = mode_rep
						elif my_rep < two_std_above and my_rep >= one_std_above:
							if my_rep > mode_rep:
								optimal_rep = my_rep
							else:
								optimal_rep = mode_rep
						elif my_rep < one_std_above and my_rep >= average_rep:
							if my_rep > mode_rep:
								optimal_rep = my_rep
							else:
								optimal_rep = mode_rep
						else:
							if average_rep > mode_rep:
								optimal_rep = average_rep
							else:
								optimal_rep = mode_rep

				else:
					if not upper_outlier:
						if my_rep > max_rep:
							if my_rep <= three_std_above:
								optimal_rep = my_rep
							else:
								optimal_rep = max_rep
						else:
							optimal_rep = max_rep
					else:
						optimal_rep = mode_rep

			else:
				if not lower_outlier:
					if not upper_outlier:
						if my_rep > max_rep:
							if my_rep <= three_std_above:
								optimal_rep = my_rep
							else:
								optimal_rep = max_rep
						else:
							optimal_rep = max_rep

					else:
						if my_rep <= three_std_above and my_rep >= two_std_above:
							optimal_rep = my_rep
						elif my_rep < two_std_above and my_rep >= one_std_above:
							optimal_rep = my_rep
						elif my_rep < one_std_above and my_rep >= average_rep:
							optimal_rep = my_rep
						else:
							optimal_rep = average_rep

				else:
					if not upper_outlier:
						if my_rep > max_rep:
							if my_rep <= three_std_above:
								optimal_rep = my_rep
							else:
								optimal_rep = max_rep
						else:
							optimal_rep = max_rep
					else:
						sig_size = len(rep_List) * (1 / 10)
						mode_count = 0
						for x in rep_List:
							if mode_rep == x:
								mode_count += 1
						if mode_count >= sig_size:
							sig_mode = True

						if sig_mode:
							optimal_rep = mode_rep

						else:
							if my_rep <= three_std_above and my_rep >= two_std_above:
								optimal_rep = my_rep
							elif my_rep < two_std_above and my_rep >= one_std_above:
								optimal_rep = my_rep
							elif my_rep < one_std_above and my_rep >= average_rep:
								optimal_rep = my_rep
							else:
								optimal_rep = average_rep

		else:
			if not lower_outlier:
				if not upper_outlier:
					if my_rep > max_rep:
						if my_rep <= three_std_above:
							optimal_rep = my_rep
						else:
							optimal_rep = max_rep
					else:
						optimal_rep = max_rep

				else:
					if my_rep <= three_std_above and my_rep >= two_std_above:
						optimal_rep = my_rep
					elif my_rep < two_std_above and my_rep >= one_std_above:
						optimal_rep = my_rep
					elif my_rep < one_std_above and my_rep >= average_rep:
						optimal_rep = my_rep
					else:
						optimal_rep = average_rep

			else:
				if not upper_outlier:
					if my_rep > max_rep:
						if my_rep <= three_std_above:
							optimal_rep = my_rep
						else:
							optimal_rep = max_rep
					else:
						optimal_rep = max_rep
				else:
					if my_rep <= three_std_above and my_rep >= two_std_above:
						optimal_rep = my_rep
					elif my_rep < two_std_above and my_rep >= one_std_above:
						optimal_rep = my_rep
					elif my_rep < one_std_above and my_rep >= average_rep:
						optimal_rep = my_rep
					else:
						optimal_rep = average_rep

def round_end(award, m, number_hunters):
	global awardPlot
	global mPlot
	global number_huntersPlot

	number_huntersPlot.append(number_hunters)
	awardPlot.append(award)
	mPlot.append(m)

